#include <Python.h>
#include "structmember.h"
#include <time.h>
#include <sys/event.h>
#include <unistd.h>

/*
 * Declares Event Class
 */
typedef struct {
    PyObject_HEAD
    struct kevent e;
} kqueue_EventObject;

static PyMemberDef kqueue_Event_members[] = {
    {"ident",  T_UINT,   offsetof(kqueue_EventObject, e.ident)},
    {"filter", T_SHORT,  offsetof(kqueue_EventObject, e.filter)},
    {"flags",  T_USHORT, offsetof(kqueue_EventObject, e.flags)},
    {"fflags", T_UINT,   offsetof(kqueue_EventObject, e.fflags)},
    {"data",   T_INT,    offsetof(kqueue_EventObject, e.data)},
    {"udata",  T_INT,    offsetof(kqueue_EventObject, e.udata)},
    {NULL}  /* Sentinel */
};

static int
kqueue_Event_init(kqueue_EventObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"ident", "filter", "flags", "fflags", "data", "udata", NULL};
    
    // defaults
    self->e.ident  = 0;
    self->e.filter = 0;
    self->e.flags  = 0;
    self->e.fflags = 0;
    self->e.data   = 0;
    self->e.udata  = 0;
    
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "ihH|Iii:Event", kwlist,
                                    &(self->e.ident),
                                    &(self->e.filter),
                                    &(self->e.flags),
                                    &(self->e.fflags),
                                    &(self->e.data),
                                    &(self->e.udata)))
        return -1;
    return 0;
}

static PyTypeObject kqueue_EventType = {
    PyObject_HEAD_INIT(NULL)
    0,                          /*ob_size*/
    "kqueue.Event",             /*tp_name*/
    sizeof(kqueue_EventObject), /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    0,                         /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_compare*/
    0,                         /*tp_repr*/
    0,                         /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    0,                         /*tp_hash */
    0,                         /*tp_call*/
    0,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT,        /*tp_flags*/
    "Event objects",           /* tp_doc */
    0,		               /* tp_traverse */
    0,		               /* tp_clear */
    0,		               /* tp_richcompare */
    0,		               /* tp_weaklistoffset */
    0,		               /* tp_iter */
    0,		               /* tp_iternext */
    0,                         /* tp_methods */
    kqueue_Event_members,      /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)kqueue_Event_init, /* tp_init */
    0,                         /* tp_alloc */
    0,                         /* tp_new */
};

/*
 * Wrap kqueue system call
 */
static PyObject *
_kqueue(PyObject *self, PyObject *args)
{
    int fd;

    if (!PyArg_ParseTuple(args, "")) /* No arguments */
        return NULL;

    fd = kqueue();

    return Py_BuildValue("i", fd);
}

/*
 * Wrap kevent system call
 */
static PyObject *
_kevent(PyObject *self, PyObject *args)
{
    int kq, nchanges, nevents;
    int num = 0;
    int i;

    struct kevent *chlist;
    struct kevent *evlist;

    PyObject *timeout = NULL;
    PyObject *changelist = NULL;
    PyObject *eventlist = NULL;

    struct timespec *p_timeout;

    if (!PyArg_ParseTuple(args, "iO!iiO", &kq,
                          &PyList_Type, &changelist, &nchanges,
                          &nevents, &timeout))
        return NULL;

    if (timeout == Py_None) {
        p_timeout = NULL;
    }

    /* Initialize chlist (C) */
    if (!(chlist = PyMem_Malloc(nchanges * sizeof(struct kevent)))) {
        PyErr_SetFromErrno(PyExc_MemoryError);
        Py_DECREF(changelist);
        return NULL;
    }
    
    /* Initialize evlist (C) */
    if (!(evlist = PyMem_Malloc(nevents * sizeof(struct kevent)))) {
        PyErr_SetFromErrno(PyExc_MemoryError);
        Py_DECREF(changelist);
        PyMem_Free(chlist);
        return NULL;
    }

    /* Fill the changelist (Python) */
    for (i = 0; i < nchanges; i++) {
        PyObject *ei = PyList_GET_ITEM(changelist, i);
        if (ei->ob_type != &kqueue_EventType) {
            PyErr_SetString(PyExc_TypeError, "arguments must be kqueue.Event objects");
            Py_DECREF(changelist);
            PyMem_Free(chlist);
            PyMem_Free(evlist);
            return NULL;
        } else {
            chlist[i] = ((kqueue_EventObject *)ei)->e;
        }
    }

    /* free changelist (Python), it's no longer used. */
    // Py_DECREF(changelist);

    Py_BEGIN_ALLOW_THREADS
    num = kevent(kq, chlist, nchanges, evlist, nevents, p_timeout);
    Py_END_ALLOW_THREADS

    /* free chlist (C) */
    PyMem_Free(chlist);

    if (num == -1) {
        return Py_BuildValue("(i,O)", num, Py_None);
    }

    if ((eventlist = PyList_New(num)) == NULL) {
        PyMem_Free(evlist);
        return Py_BuildValue("(i,O)", num, Py_None);
    }

    if (num == 0) {
    	return Py_BuildValue("(i,O)", num, PyList_New(0));
    }

    /* Fill the eventlist (Python) */
    for (i = 0; i < num; i++) {
        kqueue_EventObject *ke = PyObject_New(kqueue_EventObject, &kqueue_EventType);

        if (!ke) {
            Py_DECREF(eventlist);
            PyMem_Free(evlist);
            return NULL;
        }

        ke->e = evlist[i];
        PyList_SET_ITEM(eventlist, i, (PyObject *)ke);
    }
    
    /* Free memory and return */
    PyMem_Free(evlist);
    return Py_BuildValue("(i,O)", num, eventlist);
}

static PyMethodDef KqueueMethods[] = 
{
    {"kqueue", _kqueue, METH_VARARGS, "kqueue() system call"},
    {"kevent", _kevent, METH_VARARGS, "kevent() system call"},
    {NULL, NULL, 0, NULL}
};

PyMODINIT_FUNC
init_kqueue(void)
{
    PyObject *m;
    
    kqueue_EventType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&kqueue_EventType) < 0)
        return;

    m = Py_InitModule("_kqueue", KqueueMethods);

    if (m == NULL)
        return;

    Py_INCREF(&kqueue_EventType);
    PyModule_AddObject(m, "Event", (PyObject *)&kqueue_EventType);
}